package cn.exrick.xboot.config.security.jwt;

import cn.exrick.xboot.common.constant.SecurityConstant;
import cn.exrick.xboot.common.utils.ResponseUtil;
import cn.exrick.xboot.common.utils.SecurityUtil;
import cn.exrick.xboot.common.vo.TokenUser;
import cn.hutool.core.util.StrUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Exrickx
 */
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter   {

    private Boolean tokenRedis;

    private Integer tokenExpireTime;

    private Boolean storePerms;

    private StringRedisTemplate redisTemplate;

    private SecurityUtil securityUtil;

    private TokenStore tokenStore;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, Boolean tokenRedis, Integer tokenExpireTime,
                                   Boolean storePerms, StringRedisTemplate redisTemplate, SecurityUtil securityUtil,TokenStore tokenStore) {
        super(authenticationManager);
        this.tokenRedis = tokenRedis;
        this.tokenExpireTime = tokenExpireTime;
        this.storePerms = storePerms;
        this.redisTemplate = redisTemplate;
        this.securityUtil = securityUtil;
        this.tokenStore = tokenStore;
    }

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader(SecurityConstant.HEADER);
        if(StrUtil.isBlank(header)){
            header = request.getParameter(SecurityConstant.HEADER);
        }
        if (StrUtil.isBlank(header)){
            header = request.getParameter(SecurityConstant.OAUTH_HEARD);
        }
        //判断是否拥有oauth的请求token
        String authToken = request.getHeader("authorization");
        if (null != authToken){
            authToken = StrUtil.subAfter(authToken," ",true);
            //通过/oauth/token 生成的token 获取对应的授权信息，将其注入到上下文环境中
            OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(authToken);
            if (oAuth2Authentication == null){
                    log.error("oauth认证信息为空,authToken为" + authToken);
                    ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"oauth认证信息为空"));
                    return;
            }
            Authentication  authentication = oAuth2Authentication.getUserAuthentication();
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }else {
            Boolean notValid = StrUtil.isBlank(header) || (!tokenRedis && !header.startsWith(SecurityConstant.TOKEN_SPLIT));
            if (notValid) {
                chain.doFilter(request, response);

                return;
            }
            try {

                UsernamePasswordAuthenticationToken authentication = getAuthentication(request,header, response);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }catch (Exception e){
                e.toString();
            }
        }

        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request,String header, HttpServletResponse response) {


        // 用户名
        String username = null;
        // 权限
        List<GrantedAuthority> authorities = new ArrayList<>();

        if(tokenRedis){
            // redis
            String v = null;
            v = redisTemplate.opsForValue().get(SecurityConstant.TOKEN_PRE + header);

            if(StrUtil.isBlank(v)){
                //进行判断app
                v = redisTemplate.opsForValue().get(SecurityConstant.APP_TOKEN_PRE + header);
                if (StrUtil.isBlank(v)){
                    /**
                     * njp add 优化登录失效状态的单点返回内容  20200611 begin
                     */
                    String oAuthurl=request.getRequestURI();
                    if("/oauth/authorize".equals(oAuthurl)){
                        //ResponseUtil.out(response, ResponseUtil.resultMap(false,400,"登录已失效，请重新登录"));
                        try {
                            response.getWriter().close();
                        } catch (IOException e) {
                        e.printStackTrace();
                    }
                        return null;
                    }
                    /**
                     * njp add 优化登录失效  20200611 end
                     */
                    ResponseUtil.out(response, ResponseUtil.resultMap(false,401,"登录已失效，请重新登录"));
                    return null;
                }
            }
            TokenUser user = new Gson().fromJson(v, TokenUser.class);

            username = user.getUsername();

            /**
             * 添加三权分立管理员登录一天失效
             */
          /*  if(CommonConstant.ADMINISTRATOR.equals(username)){

            }*/
            if(storePerms){
                // 缓存了权限
                for(String ga : user.getPermissions()){
                    authorities.add(new SimpleGrantedAuthority(ga));
                }
            }else{
                // 未缓存 读取权限数据
                authorities = securityUtil.getCurrUserPerms(username);
            }

            //if(!user.getSaveLogin()){
            if(true){
                //add 20200527 njp 在原有的基础上添加一个接口的校验为了适应前段的接口轮训问题，因为轮训会导致用户一定时间不操作剔除登录列表失败 begin
                //注意：这个逻辑只影响url一个接口地址

                //忽略更新token时间的接口地址（因为前段这个接口使用了循环调用的逻辑  晕。。。）
                String url=request.getRequestURI();
                if("/xboot/portalController/selectBacklogOrFullListByMap".equals(url)){
                   return null;
                }
                //add njp 在原有的基础上添加一个接口的校验为了适应前段的接口轮训问题，因为轮训会导致用户一定时间不操作剔除登录列表失败 end

                // 若未保存登录状态重新设置失效时间
                redisTemplate.opsForValue().set(SecurityConstant.USER_TOKEN + username, header, tokenExpireTime, TimeUnit.MINUTES);
                redisTemplate.opsForValue().set(SecurityConstant.TOKEN_PRE + header, v, tokenExpireTime, TimeUnit.MINUTES);
            }
           /* if(!user.getSaveLogin()){
                System.out.println("isApp的值==" + isApp);
                if (isApp = false){
                    // 若未保存登录状态重新设置失效时间
                    redisTemplate.opsForValue().set(SecurityConstant.USER_TOKEN + username, header, tokenExpireTime, TimeUnit.MINUTES);
                    redisTemplate.opsForValue().set(SecurityConstant.TOKEN_PRE + header, v, tokenExpireTime, TimeUnit.MINUTES);
                }
            }*/
        }else{
            // JWT
            try {
                // 解析token
                Claims claims = Jwts.parser()
                        .setSigningKey(SecurityConstant.JWT_SIGN_KEY)
                        .parseClaimsJws(header.replace(SecurityConstant.TOKEN_SPLIT, ""))
                        .getBody();

                // 获取用户名
                username = claims.getSubject();
                // 获取权限
                if(storePerms) {
                    // 缓存了权限
                    String authority = claims.get(SecurityConstant.AUTHORITIES).toString();
                    if(StrUtil.isNotBlank(authority)){
                        List<String> list = new Gson().fromJson(authority, new TypeToken<List<String>>(){}.getType());
                        for(String ga : list){
                            authorities.add(new SimpleGrantedAuthority(ga));
                        }
                    }
                }else{
                    // 未缓存 读取权限数据
                    authorities = securityUtil.getCurrUserPerms(username);
                }
            } catch (ExpiredJwtException e) {
                ResponseUtil.out(response, ResponseUtil.resultMap(false,401,"登录已失效，请重新登录"));
            } catch (Exception e){
                log.error(e.toString());
                ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"解析token错误"));
            }
        }

        if(StrUtil.isNotBlank(username)) {
            //Exrick踩坑提醒 此处password不能为null
            User principal = new User(username, "", authorities);
            return new UsernamePasswordAuthenticationToken(principal, null, authorities);
        }
        return null;
    }
}

